package it.univaq.mancoosi.injectors.systemconfiguration.managers.ubuntu;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.util.EcoreUtil;

import it.univaq.mancoosi.mancoosimm.AndConflict;
import it.univaq.mancoosi.mancoosimm.AndDep;
import it.univaq.mancoosi.mancoosimm.Conflict;
import it.univaq.mancoosi.mancoosimm.Dependence;
import it.univaq.mancoosi.mancoosimm.DocumentationFile;
import it.univaq.mancoosi.mancoosimm.File;
import it.univaq.mancoosi.mancoosimm.InstalledPackage;
import it.univaq.mancoosi.mancoosimm.NotInstalledPackage;
import it.univaq.mancoosi.mancoosimm.OrConflict;
import it.univaq.mancoosi.mancoosimm.OrDep;
import it.univaq.mancoosi.mancoosimm.Package;
import it.univaq.mancoosi.mancoosimm.PackageSetting;
import it.univaq.mancoosi.mancoosimm.PriorityType;
import it.univaq.mancoosi.mancoosimm.SingleConflict;
import it.univaq.mancoosi.mancoosimm.SingleDep;
import it.univaq.mancoosi.mancoosimm.VersionType;
import it.univaq.mancoosi.injectors.systemconfiguration.managers.PackageManager;
import it.univaq.mancoosi.injectors.systemconfiguration.utils.ProcessCommand;

public class UbuntuPackageManager extends PackageManager {

	public static PackageManager getInstance(){
		if (INSTANCE == null)
			INSTANCE = new UbuntuPackageManager();
		return INSTANCE;
	}

	protected void createSingleDeps(String[] singleDeps, Dependence owner){

		SingleDep singleDep = factory.createSingleDep();
		singleDep.setPkg(pkgMap.get(singleDeps[0]));

		if (singleDeps.length != 1) {
			singleDep.setValue(singleDeps[2].substring(0, singleDeps[2].length()-1));
			String op = singleDeps[1].substring(1, singleDeps[1].length());
			singleDep.setVersion(VersionType.getByName(versionTypeMap.get(op)));
		}

		if (owner instanceof AndDep){
			((AndDep) owner).getOps().add(singleDep);
		}

		if (owner instanceof OrDep){
			((OrDep) owner).getOps().add(singleDep);
		}
	}


	protected void createSingleConflicts(String[] singleConflicts, Conflict owner){

		SingleConflict singleConflict = factory.createSingleConflict();

		Package aux = pkgMap.get(singleConflicts[0]);
		if (aux == null) {
			aux = (NotInstalledPackage) factory.createNotInstalledPackage();
			aux.setName(singleConflicts[0]);
			if (singleConflicts.length != 1) {
				singleConflict.setValue(singleConflicts[2].substring(0, singleConflicts[2].length()-1));
				String op = singleConflicts[1].substring(1, singleConflicts[1].length());
				singleConflict.setVersion(VersionType.getByName(versionTypeMap.get(op)));
			}
			configuration.getNotInstalledPackages().add((NotInstalledPackage) aux);
		} 
		singleConflict.setPkg(aux);	

		if (singleConflicts.length != 1) {
			singleConflict.setValue(singleConflicts[2].substring(0, singleConflicts[2].length()-1));
			String op = singleConflicts[1].substring(1, singleConflicts[1].length());
			singleConflict.setVersion(VersionType.getByName(versionTypeMap.get(op)));
		}

		if (owner instanceof AndConflict){
			((AndConflict) owner).getOps().add(singleConflict);
		}

		if (owner instanceof OrConflict){
			((OrConflict) owner).getOps().add(singleConflict);
		}
	}

	protected void processPackageMetadataLine(String aLine, InstalledPackage pkg){

		String[] line = aLine.split(": ");


		if (line[0].equals("Description")) {
			pkg.setDescription(line[1]);
		}

		if (line[0].equals("Version")) {
			pkg.setVersion(line[1].trim());
		}

		if (line[0].equals("Installed-Size")) {
			pkg.setInstalledSize(new Integer(line[1].trim()).intValue());
		}

		if (line[0].equals("Maintainer")) {
			pkg.setMaintainer(line[1]);
		}

		if (line[0].equals("Architecture")) {
			pkg.setArchitecture(line[1]);
		} 

		if (line[0].equals("Section")) {
			pkg.setSection(line[1]);
		}

		if (line[0].equals("Priority")) {
			pkg.setPriority(PriorityType.getByName(line[1]));
		}

		if (line[0].equals("Depends")) {
			String[] orDependences = line[1].split(" \\| ");   		    
			if (orDependences.length != 1){	
				OrDep orDep = factory.createOrDep();	
				for (int i=0; i< orDependences.length; i++){

					String[] andDependences = orDependences[i].split(", ");
					if (andDependences.length != 1){
						AndDep andDep = factory.createAndDep();
						for (int j=0; j < andDependences.length; j++) {	    				
							String[] singleDeps = andDependences[j].split(" ");    					
							createSingleDeps(singleDeps,andDep);	    					
						}	    				
						orDep.getOps().add(andDep);
					} else {	    		
						String[] singleDeps = andDependences[0].split(" ");				
						createSingleDeps(singleDeps,orDep);	    				
					}    			
				}
				pkg.setDepends(orDep);
			} else {    		
				String[] andDependences = orDependences[0].split(", ");
				if (andDependences.length != 1){
					AndDep andDep = factory.createAndDep();
					for (int j=0; j < andDependences.length; j++) {
						String[] singleDeps = andDependences[j].split(" ");					
						createSingleDeps(singleDeps,andDep);
					}
					pkg.setDepends(andDep);			
				} else {

					SingleDep singleDep = factory.createSingleDep();
					String[] singleDeps = andDependences[0].split(" ");

					singleDep.setPkg(pkgMap.get(singleDeps[0]));		
					if (singleDeps.length != 1) {
						singleDep.setValue(singleDeps[2].substring(0, singleDeps[2].length()-1));
					}
					pkg.setDepends(singleDep);
				}	
			}
		}


		if (line[0].equals("Conflicts")) {
			String[] orConflicts = line[1].split(" \\| ");		    
			if (orConflicts.length != 1){	
				OrConflict orConflict = factory.createOrConflict();	
				for (int i=0; i< orConflicts.length; i++){

					String[] andConflicts = orConflicts[i].split(", ");
					if (andConflicts.length != 1){
						AndConflict andConflict = factory.createAndConflict();
						for (int j=0; j < andConflicts.length; j++) {	    				
							String[] singleConflicts = andConflicts[j].split(" ");    					
							createSingleConflicts(singleConflicts,andConflict);	    					
						}	    				
						orConflict.getOps().add(andConflict);
					} else {	    		
						String[] singleConflicts = andConflicts[0].split(" ");				
						createSingleConflicts(singleConflicts,orConflict);	    				
					}    			
				}
				pkg.setConflict(orConflict);
			} else {	    		
				String[] andConflicts = orConflicts[0].split(", ");
				if (andConflicts.length != 1){
					AndConflict andConflict = factory.createAndConflict();
					for (int j=0; j < andConflicts.length; j++) {
						String[] singleConflicts = andConflicts[j].split(" ");					
						createSingleConflicts(singleConflicts,andConflict);
					}
					pkg.setConflict(andConflict);   				
				} else {		
					SingleConflict singleConflict = factory.createSingleConflict();
					String[] singleConflicts = andConflicts[0].split(" ");

					Package aux = pkgMap.get(singleConflicts[0]);
					if (aux == null) {
						aux = (NotInstalledPackage) factory.createNotInstalledPackage();
						aux.setName(singleConflicts[0]);
						if (singleConflicts.length != 1) {
							aux.setVersion(singleConflicts[2].substring(0, singleConflicts[2].length()-1));
						}
						configuration.getNotInstalledPackages().add((NotInstalledPackage) aux);
					} 
					singleConflict.setPkg(aux);	
					if (singleConflicts.length != 1) {
						singleConflict.setValue(singleConflicts[2].substring(0, singleConflicts[2].length()-1));
						String op = singleConflicts[1].substring(1, singleConflicts[1].length());
						singleConflict.setVersion(VersionType.getByName(versionTypeMap.get(op)));
					}
					pkg.setConflict(singleConflict);
				}	
			}
		}	
	}

	protected void processConfFiles(List<String> conffilesBlock, InstalledPackage installedPackage){
		Iterator<String> conffilesIterator = conffilesBlock.iterator();
		String conffile;
		File file;

		pkgSettingsMap.put(installedPackage, new ArrayList<File>());
		while (conffilesIterator.hasNext()){
			conffile = conffilesIterator.next();
			file = fileSystemManager.createFile(conffile);

			if ( ! installedPackage.getPackageSettings().getFiles().contains(file) ) {
				installedPackage.getPackageSettings().getFiles().add(file);
				file.getPkgSettings().add(installedPackage.getPackageSettings());
				pkgSettingsMap.get(installedPackage).add(file);
			}	
		}
	}


	protected void processPackageMetadata(BufferedReader packageInfo, InstalledPackage installedPackage) throws IOException {

		Scanner scanner = new Scanner(packageInfo);
		String line = new String();
		String[] lineFragments;
		String conffile;
		String[] auxConffile;
		try {
			//first use a Scanner to get each line
			while ( scanner.hasNextLine() ){

				// we have to check if the considered line refers to "Conffiles"
				line = scanner.nextLine();
				lineFragments = line.split(":");
				if (! (lineFragments[0].equals("Conffiles")) )  {
					processPackageMetadataLine( line, installedPackage );
				} else  {

					// A list of configuration files is create
					List<String> conffilesBlock = new ArrayList<String>();
					PackageSetting pkgSetting = factory.createPackageSetting();
					installedPackage.setPackageSettings(pkgSetting);

					while (scanner.hasNextLine() ) {
						line = scanner.nextLine();
						conffile = (line.split(":"))[0];
						if (! (conffile.equals("Description")) ){
							auxConffile = conffile.split(" ");

							if (auxConffile.length>2 && !auxConffile[2].equals("newconffile") )  { 	// This condition is necessary to do not add twice files in situation like the following:
								//     /etc/defoma/csetenc-xenc.data2 9c531837b77b5aa1eccda5908b3e52c0
								//     /etc/defoma/csetenc-xenc.data2 newconffile

								conffile = auxConffile[1];					// This is to extract the right file path from strings like " /etc/default/acpi-support aa46fcd708d0bc25ac3c53fd2c0daa15"

								conffilesBlock.add(conffile);
							}
						} else {
							break;
						}
					}

					// The files contained in the list previously created are now processed
					processConfFiles(conffilesBlock, installedPackage);	    		  
				}	  
			}
		}
		finally {
			//ensure the underlying stream is always closed
			scanner.close();
		}

	}

	public void setFeaturesOfInstalledPackages(HashMap<String, InstalledPackage> pkgMap) throws IOException {
		InstalledPackage pkg;

		//Set attributes and references for all the installed packages
		//		Iterator<InstalledPackage> i = ConfigurationManager.getInstance().getConfiguration().getInstalledPackages().iterator();
		Iterator<InstalledPackage> i = pkgMap.values().iterator();

		while (i.hasNext()) {
			pkg = i.next();
			String[] cmd1 = {"/bin/sh","-c","dpkg -s " + pkg.getName()};

			BufferedReader input = ProcessCommand.getInstance().getResult(cmd1);	
			
			System.out.println("Processing the package " + pkg.getName());
			System.out.println("	processing configuration files...");
			processPackageMetadata(input, pkg);
			//			createServiceFiles(pkg);
			createDocFiles(pkg);

			input.close();
		}
	}

	//	public void setFeaturesOfInstalledPackagesFoundBySync() throws IOException {
	//		String cmd;
	//		InstalledPackage pkg;
	//
	//		//Set attributes and references for all the installed packages
	//		Iterator<InstalledPackage> i = pkgFromSyncMap.values().iterator();
	//
	//		while (i.hasNext()) {
	//			pkg = i.next();
	//
	//			cmd = "dpkg -s " + pkg.getName();	
	//			Process p2 = Runtime.getRuntime().exec(cmd);
	//			BufferedReader pkgInfo =
	//				new BufferedReader
	//				(new InputStreamReader(p2.getInputStream()));
	//			System.out.println("Processing the new found package " + pkg.getName());
	//			System.out.println("	processing configuration files...");
	//			processPackageMetadata(pkgInfo, pkg);
	////			createServiceFiles(pkg);
	//			createDocFiles(pkg);
	//
	//			pkgInfo.close();
	//			p2.destroy();
	//
	//		}
	//	}

	public void createPackageElementsFromSystem() throws IOException {
		String line;

		String[] cmd = {"/bin/sh","-c"," dpkg -l | awk '/ii/' | awk '{print $2}'"};
		BufferedReader input = ProcessCommand.getInstance().getResult(cmd);	
		
		int i=0;
		while ((line = input.readLine()) != null/* && i<10*/) {
			createPackage(line);
			i++;
		}
		System.out.println("numero package" + i);

		input.close();			

		setFeaturesOfInstalledPackages(pkgMap);	

	}


	protected void setNotInstPkgRefAfterSync (NotInstalledPackage pkg) {

		Collection<Setting> pkgreferences = EcoreUtil.UsageCrossReferencer.find(pkg,
				UbuntuConfigurationManager.getInstance().getConfiguration() );

		Iterator<Setting> iter = pkgreferences.iterator();
		while (iter.hasNext()) {
			EObject obj = iter.next().getEObject();
			if (obj instanceof SingleConflict)
				((SingleConflict)obj).setPkg(getInstalledPackageByName(pkg.getName()));

			else if (obj instanceof SingleDep)
				((SingleDep)obj).setPkg(getInstalledPackageByName(pkg.getName()));
		}

		/*
		EList<InstalledPackage> listPkg = configuration.getInstalledPackages();
		Iterator<InstalledPackage> iterator = listPkg.iterator(); 
		while (iterator.hasNext()) {
			InstalledPackage currPkg = iterator.next();
			Conflict conflict = currPkg.getConflict();
			if (conflict != null) {
				if (conflict instanceof SingleConflict &&
						conflict.getPkg().getName().equals(pkg.getName())) {
					InstalledPackage temp = getInstalledPackageByName("alsa-utils"//pkg.getName()
							);
					conflict.setPkg(temp);
					if (temp == null) {
						if (((SingleConflict)conflict).getValue() != null) 
							((SingleConflict)conflict).setValue(null);
						if (((SingleConflict)conflict).getVersion() != null) 
							((SingleConflict)conflict).setVersion(null);
					}
					return;
				}
				else if (conflict instanceof AndConflict) {
					Iterator<Conflict> AndIterator = ((AndConflict)conflict).getOps().iterator();
					while (AndIterator.hasNext()) {
						SingleConflict singleConflict = (SingleConflict)AndIterator.next();
						if (singleConflict.getPkg() != null && 
								singleConflict.getPkg().getName().equals(pkg.getName())) {
							InstalledPackage temp = getInstalledPackageByName("alsa-utils"//pkg.getName()
							);
							singleConflict.setPkg(temp);
							if (temp == null) {
								if (singleConflict.getValue() != null) 
									singleConflict.setValue(null);
								if (singleConflict.getVersion() != null) 
									singleConflict.setVersion(null);
							}
							return;
						}
					}
				}

				else if (conflict instanceof OrConflict) {
					Iterator<Conflict> OrIterator = ((OrConflict)conflict).getOps().iterator();
					while (OrIterator.hasNext()) {
						SingleConflict singleConflict = (SingleConflict)OrIterator.next();
						if (singleConflict.getPkg() != null && 
								singleConflict.getPkg().getName().equals(pkg.getName())) {
							InstalledPackage temp = getInstalledPackageByName("alsa-utils"//pkg.getName()
							);
							singleConflict.setPkg(temp);
							if (temp == null) {
								if (singleConflict.getValue() != null) 
									singleConflict.setValue(null);
								if (singleConflict.getVersion() != null) 
									singleConflict.setVersion(null);
							}
							return;
						}
					}
				}
			}
		}
		 */
	}

	public int countPkgInTheSystem() throws IOException {
		String line;

		String[] cmd = {"/bin/sh","-c"," dpkg -l | awk '/ii/' | awk '{print $2}'| wc | awk '{print $1}'"};
		BufferedReader input = ProcessCommand.getInstance().getResult(cmd);	
	
		line = input.readLine();

		System.out.println("numero package" + line);

		int n = Integer.parseInt (line);

		input.close();			
		return n;
	}

	public void synchronize() throws IOException {
		syncMap();
		String packageName;

		System.out.println("Synchronizing the installed packages...");
		String[] cmd = {"/bin/sh","-c"," dpkg -l | awk '/ii/' | awk '{print $2}'"};
		BufferedReader input = ProcessCommand.getInstance().getResult(cmd);	

		int i = 0;
		while ((packageName = input.readLine()) != null/* && i<22*/) {
			if ( ! existsPackage(packageName) ){
				System.out.println("Found the new package " + packageName);    			
				pkgFromSyncMap.put(packageName, createPackage(packageName));
			};
			i++;
		}

		setFeaturesOfInstalledPackages(pkgFromSyncMap);

		int numPkgModello = 0;
		if(UbuntuConfigurationManager.getInstance().getConfiguration() != null){
			numPkgModello = UbuntuConfigurationManager.getInstance().getConfiguration().getInstalledPackages().size();
		}
		int numPkgAggiunti = pkgFromSyncMap.size();

		int pkgInTheSystem = countPkgInTheSystem();

		if(((numPkgModello + numPkgAggiunti) != pkgInTheSystem)){
			EList<InstalledPackage> listPkg = configuration.getInstalledPackages();
			Iterator<InstalledPackage> iterator = listPkg.iterator(); 

			while (iterator.hasNext()) {
				InstalledPackage pkg = iterator.next();

				if (!existsInTheSystem(pkg.getName())) {

					Collection<Setting> pkgreferences = EcoreUtil.UsageCrossReferencer.find(pkg,
							UbuntuConfigurationManager.getInstance().getConfiguration() );

					if (pkgreferences.size() > 0) {
						NotInstalledPackage aux = factory.createNotInstalledPackage();
						aux.setName(pkg.getName());
						configuration.getNotInstalledPackages().add(aux);

						Iterator<Setting> iter = pkgreferences.iterator();
						while (iter.hasNext()) {
							EObject obj = iter.next().getEObject();
							if (obj instanceof SingleConflict)
								((SingleConflict)obj).setPkg(aux);

							else if (obj instanceof SingleDep)
								((SingleDep)obj).setPkg(aux);
						}
					}

					instPkgToBeDeleted.add(pkg);
					removePackage(pkg);
					pkgMap.remove(pkg.getName());
				}		
			}    	

			for(int j=0; j<instPkgToBeDeleted.size(); j++){
				EcoreUtil.delete(instPkgToBeDeleted.get(j));
			}

			input.close();			
		}

		//update not-installed package
		EList<NotInstalledPackage> listNotInstPkg = configuration.getNotInstalledPackages();
		Iterator<NotInstalledPackage> iter = listNotInstPkg.iterator(); 
		while (iter.hasNext()) {
			NotInstalledPackage pkg = iter.next();

			if (existsInTheSystem(pkg.getName())) {
				setNotInstPkgRefAfterSync(pkg);
				notInstPkgToBeDeleted.add(pkg);
			}
		}

		for(int j=0; j<notInstPkgToBeDeleted.size(); j++){
			EcoreUtil.delete(notInstPkgToBeDeleted.get(j));
		}
	}

	//	@Override
	//	protected void createServiceFiles(InstalledPackage installedPackage) throws IOException{
	//
	//		String line;
	//
	//		String[] cmd = {"/bin/sh","-c"," dpkg -L " + installedPackage.getName()};
	//		Process p = Runtime.getRuntime().exec(cmd);
	//		BufferedReader input =
	//			new BufferedReader
	//			(new InputStreamReader(p.getInputStream()));
	//
	//		while ((line = input.readLine()) != null) {
	//			java.io.File file = new java.io.File(line);
	//			if(!file.isDirectory()){
	//				if(isExecutable(file)){
	//					createService(installedPackage, file);
	//				}
	//			}
	//		}
	//		input.close();
	//	}

	//	protected boolean isExecutable(java.io.File file){
	//
	//		boolean result = false;
	//		String line = new String();
	//
	//		try {
	//
	//			String[] cmd = {"/bin/sh","-c", "ls --time-style=long-iso -al " + file.getPath() + " | awk '{print $1}'"};
	//			Process p = Runtime.getRuntime().exec(cmd);
	//			BufferedReader input =
	//				new BufferedReader
	//				(new InputStreamReader(p.getInputStream()));
	//
	//
	//			while ((line = input.readLine()) != null) {
	//				if(line.contains("x")){
	//					result = true;
	//				}else{
	//					result = false;
	//				}
	//			}			
	//
	//		} catch (Exception err) {
	//			err.printStackTrace();
	//		}
	//
	//		return result;
	//	}

	//	protected void createService(InstalledPackage installedPackage, java.io.File file) throws IOException {
	//
	//		Service newService; 
	//		File serviceModelFile = null;
	//
	//		if(file.exists()){
	//			serviceModelFile = UbuntuFileSystemManager.getInstance()
	//			.createFile(file.getPath());
	//			newService = factory.createService();
	//			newService.setExecutable(serviceModelFile);
	//			newService.setPackage(installedPackage);
	//			UbuntuEnvironmentManager.getInstance().getEnvironment().getRunningServices().add(newService);
	//			installedPackage.getServices().add(newService);	
	//		}			
	//
	//	}

	protected void createDocumentationFile(InstalledPackage installedPackage, java.io.File file) throws IOException {

		DocumentationFile newDocFile; 

		if(file.exists()){
			UbuntuFileSystemManager.getInstance().createFile(file.getPath());
		}			
		newDocFile = factory.createDocumentationFile();
		newDocFile.setName(file.getName());
		newDocFile.setPkg(installedPackage);
		installedPackage.getDocumentationFiles().add(newDocFile);		
	}

	@Override
	protected void createDocFiles(InstalledPackage installedPackage) throws IOException {

		String line;

		String[] cmd = {"/bin/sh","-c"," dpkg -L " + installedPackage.getName()};
		BufferedReader input = ProcessCommand.getInstance().getResult(cmd);	

		java.io.File docDirectory = null;
		while ((line = input.readLine()) != null) {

			if(line.equals("/usr/share/doc/" + installedPackage.getName())){
				docDirectory = new java.io.File("/usr/share/doc/" + installedPackage.getName());
			}

			if(docDirectory != null && docDirectory.exists()){
				if(line.startsWith("/usr/share/doc/" + installedPackage.getName()) && !line.equals("/usr/share/doc/" + installedPackage.getName())){
					java.io.File file = new java.io.File("/usr/share/doc/" + installedPackage.getName() + "/" + line);
					createDocumentationFile(installedPackage, file);
				}
			}
		}
		input.close();		
	}

	public boolean existsInTheSystem (String pkgName) {

		boolean exists = true;
		String line;

		try {
			String[] cmd = {"/bin/sh","-c","dpkg -s " + pkgName};
			
			BufferedReader input = ProcessCommand.getInstance().getErrorSream(cmd);	

			while ((line = input.readLine()) != null){
				if(line.contains("not-installed") || line.contains("is not installed")){
					exists = false;
					return exists;
				}

			}

			input.close();
		}
		catch (Exception e) {
			e.printStackTrace();
		}

		return exists;
	}
}